#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h> /* getopt_long() */
#include <fcntl.h> /* low-level i/o */
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
// #include <thread>

#define CLEAR(x) memset(&(x), 0, sizeof(x))

using namespace std;

class v4l2
{
private: 
    unsigned int n_buffers;
    int frame_count = 70;    //只采集60帧
    struct buffer 
    {
        void *start;
        size_t length;
        char ack[3] = {0xAA,0x45,0xFF};
    };
    buffer *buffers;
    struct sockaddr_in serv_addr;
    const char* ipAddr = "192.168.3.3";
    int port = 21;
    int tcp_error_cnt = 0;
    
private:
    int xioctl(int fh, int request, void *arg);
    void errno_exit(const char *s);
    void process_image(const void *p, int size);
    int read_frame(FILE *fp);
    void init_mmap(void);
    void tcpip_pass(const void *p, int size);
    void UDP_pass(const void *p, int size);

public:
    //int sock = socket(AF_INET, SOCK_STREAM, 0);//tcp init
    int sock = socket(AF_INET,SOCK_DGRAM,0);
    int fd = -1;       //DEVICE NUMBR
    char *dev_name; //摄像头名称
    FILE *fp; 
    
public:
    void open_device(void);
    void init_device(void);
    void start_capturing(void);
    void mainloop(void);
    void stop_capturing(void);
    void uninit_device(void);
    void close_device(void);
    void tcpip_init(void);
};

inline int v4l2::xioctl(int fh, int request, void *arg)
{
    int r;
    do 
    {
        r = ioctl(fh, request, arg);
    } while (-1 == r && EINTR == errno);
    return r;
}

inline void v4l2::errno_exit(const char *s)
{
        fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
        exit(EXIT_FAILURE);
}

inline void v4l2::process_image(const void *p, int size)
{
    fwrite(p,size, 1, fp); 
    cout << size <<endl;
}

inline void v4l2::tcpip_pass(const void *p, int size)
{
    char ackbuff[3];
    send(sock,p,size,MSG_WAITALL);
    recv(sock, ackbuff, sizeof(ackbuff),MSG_WAITALL);
    cout << size <<endl;
    
}

inline void v4l2::UDP_pass(const void *p, int size)
{
    int send_num = -1;
    send_num = sendto(sock,p,size,0,(const sockaddr *)&serv_addr,sizeof(serv_addr));
    if (send_num < 0)
    {
         errno_exit("UDPPASSERROR");
    }
    
    cout << size <<endl;
    
}
void v4l2::tcpip_init(void)
{ 
    int value=43009000;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr(ipAddr);  //具体的IP地址
    serv_addr.sin_port = htons(port);  //端口
     setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&value, sizeof(value));
    // int ret = connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    // while(ret==-1)
    // {
    //     tcp_error_cnt++;
    //     ret = connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    //     if (tcp_error_cnt >5)
    //     {
    //         errno_exit("TCPIPPASSERROR");
    //     }
    // }
    //tcp_error_cnt = 0;
    // int flags = fcntl(sock, F_GETFL, 0);         //获取文件的flags值。
    // fcntl(sock, F_SETFL, flags | O_NONBLOCK);   //设置成非阻塞模式；
    // int flags  = fcntl(sock,F_GETFL,0);
    // fcntl(sock,F_SETFL,flags&~O_NONBLOCK);    //设置成阻塞模式；
}

int v4l2::read_frame(FILE *fp)
{
    struct v4l2_buffer buf;
    CLEAR(buf);

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    
    if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf))
    {
        errno_exit("VIDIOC_DQBUF");
    } 

    // process_image(buffers[buf.index].start, buf.bytesused);    
    // tcpip_pass(buffers[buf.index].start, buf.bytesused);
    UDP_pass(buffers[buf.index].start, buf.bytesused);

    if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
    {
        errno_exit("VIDIOC_QBUF"); 
    }
    return 1;
}

void v4l2::init_mmap(void)
{
    struct v4l2_requestbuffers req;

    CLEAR(req);

    req.count = 4;                          //缓冲帧数量
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //视频捕获模式
    req.memory = V4L2_MEMORY_MMAP;

    if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) 
    {
        if (EINVAL == errno) 
        {
            cout<< stderr << dev_name << "不支持mmap存储" <<endl;
            exit(EXIT_FAILURE);
        } 
        else 
        {
            errno_exit("VIDIOC_REQBUFS");
        }
    }

    if (req.count < 2) {
         cout<< stderr << dev_name << "缓存区域不足" <<endl;
            exit(EXIT_FAILURE);
    }
    //内存映射
    buffers = (buffer*)calloc(req.count, sizeof(*buffers));

    if (!buffers) 
    {
        cout<< stderr << "内存溢出" <<endl;
        exit(EXIT_FAILURE);
    }

    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) 
    {
        struct v4l2_buffer buf;

        CLEAR(buf);

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;

        if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
        {
            errno_exit("VIDIOC_QUERYBUF");
        }
                

        buffers[n_buffers].length = buf.length;
        buffers[n_buffers].start =
                        mmap(NULL /* start anywhere */,
                        buf.length,
                        PROT_READ | PROT_WRITE /* required */,
                        MAP_SHARED /* recommended */,
                        fd, buf.m.offset);

        if (MAP_FAILED == buffers[n_buffers].start)
        {
            errno_exit("mmap");
        }
                
    }
}

void v4l2::open_device(void)
{
    fd = open(dev_name, O_RDWR /*| O_NONBLOCK*/, 0); //阻塞打开摄像头

    if (-1 == fd)
    {
        cout << "无法打开摄像头，请检查" << endl;
        exit(EXIT_FAILURE);
    }
    cout << "成功打开摄像头"<< dev_name <<endl;
}

void v4l2::init_device(void)
{
    struct v4l2_capability cap; //读取设备设备信息的结构体
    struct v4l2_format fmt;     //设置设备参数的结构体
    struct v4l2_fmtdesc fmtdesc;//设备所支持的图片格式
    struct v4l2_format Format;  //得到设备参数的结构体


    if(-1 == xioctl(fd,VIDIOC_QUERYCAP,&cap))
    {
        if (EINVAL == errno) //参数无效
        {
            cout<< stderr << dev_name << "不支持V4L2驱动" <<endl;
            exit(EXIT_FAILURE);
        }
        else
        {
            errno_exit("VIDIOC_QUERYCAP");
        } 
    }

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) 
    {
        cout<< stderr << dev_name << "不是一个视频捕捉设备" <<endl;
        exit(EXIT_FAILURE);
    }

    if (!(cap.capabilities & V4L2_CAP_STREAMING)) 
    {
        cout<< stderr << dev_name << "不有数据流控制模式" <<endl;
        exit(EXIT_FAILURE);
    }


    fmtdesc.index = 0;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(-1 == xioctl(fd,VIDIOC_ENUM_FMT,&fmtdesc))
    {
        errno_exit("VIDIOC_ENUM_FMT");
    }
    else
    {
        for (size_t i = 0; i < 32; i++)
        {
            cout << fmtdesc.description[i];
        }
         
    }
    
    // memset(&Format, 0, sizeof(struct v4l2_format));
    // Format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    // if(-1 == xioctl(fd,VIDIOC_G_FMT,&fmtdesc))
    // {
    //     errno_exit("VIDIOC_G_FMT");
    // }
    // else
    // {
    //     cout << Format.fmt << endl;
    // }   
    CLEAR(fmt); 
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //视频捕捉模式
    fmt.fmt.pix.width = 176;
    fmt.fmt.pix.height = 144;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//YUYV格式
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

    if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))        //设置格式
    {
        cout<< stderr << "设置模式失败" <<endl;
        exit(EXIT_FAILURE);
    }                 
        init_mmap();


}

void v4l2::start_capturing(void)
{
    unsigned int i;
    enum v4l2_buf_type type;

    for (i = 0; i < n_buffers; ++i) 
    {
        struct v4l2_buffer buf;

        CLEAR(buf);
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
        {
            errno_exit("VIDIOC_QBUF");
        }         
    }
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
    {
        errno_exit("VIDIOC_STREAMON");
    }
            
}

void v4l2::mainloop(void)
{
        unsigned int count;
        count = frame_count;
        // while (count-- > 0) 
        while(1)
        {
            printf("No.%d\n",frame_count - count);        //显示当前帧数目
            read_frame(fp);
        }
        printf("\nREAD AND SAVE DONE!\n");
}

void v4l2::stop_capturing(void)
{
        enum v4l2_buf_type type;

        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
        {
            errno_exit("VIDIOC_STREAMOFF");
        }
            
}

void v4l2::uninit_device(void)
{
    unsigned int i;

    for (i = 0; i < n_buffers; ++i)
    if (-1 == munmap(buffers[i].start, buffers[i].length))
    {
        errno_exit("munmap");
    }
    free(buffers);
}

void v4l2::close_device(void)
{
        if (-1 == close(fd))
                errno_exit("close");

        fd = -1;
}

int main(int argc, char **argv)
{
    v4l2 myCamera;
    if(argc != 3) //俩个参数，保存文件的名字，摄像头设备名称
    {
        cout<<"请输入类似: ./app video.yuv /dev/video0 "<<endl;
        exit(0);
    }
    myCamera.dev_name = argv[2];
    myCamera.open_device();
    if ((myCamera.fp = fopen(argv[1], "w")) == NULL) 
    { 
        cout << "创建文件失败" << endl;
        exit(0); 
    }

    myCamera.open_device();             //打开摄像头
    myCamera.init_device();             //初始化摄像头参数
    myCamera.tcpip_init();
    myCamera.start_capturing();         //开始采集
    myCamera.mainloop();                //主要处理均在该函数中实现
    close(myCamera.sock);
    fclose(myCamera.fp);                //关闭文件
    myCamera.stop_capturing();          //停止采集
    myCamera.uninit_device();          
    myCamera.close_device();
    return 0;
}